/*
AppleWin : An Apple //e emulator for Windows

Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2007, Tom Charlesworth, Michael Pohoreski

AppleWin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

AppleWin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* Description: Joystick emulation via keyboard, PC joystick or mouse
 *
 * Author: Various
 */

// TC: Extended for 2nd joystick:
// Apple joystick #0 can be emulated with: NONE, JOYSTICKID1, KEYBOARD, MOUSE
// Apple joystick #1 can be emulated with: NONE, JOYSTICKID2, KEYBOARD, MOUSE
// If Apple joystick #0 is using {KEYBOARD | MOUSE} then joystick #1 can't use it.
// If Apple joystick #1 is using KEYBOARD, then disable the standard keys that control Apple switches #0/#1.
// - So that in a 2 player game, player 2 can't cheat by controlling player 1's buttons.
// If Apple joystick #1 is not NONE, then Apple joystick #0 only gets the use of Apple switch #0.
// - When using 2 joysticks, button #1 is used by joystick #1 (Archon).
// Apple joystick #1's button now controls Apple switches #1 and #2.
// - This is because the 2-joystick version of Mario Bros expects the 2nd joystick to control Apple switch #2.

#include "StdAfx.h"

#include "Joystick.h"
#include "CPU.h"
#include "Memory.h"
#include "YamlHelper.h"
#include "Interface.h"
#include "CopyProtectionDongles.h"

enum {DEVICE_NONE=0, DEVICE_JOYSTICK, DEVICE_KEYBOARD, DEVICE_MOUSE, DEVICE_JOYSTICK_THUMBSTICK2};

// Indexed by joytype[n]
static const uint32_t joyinfo[6] =	{	DEVICE_NONE,
									DEVICE_JOYSTICK,
									DEVICE_KEYBOARD,	// Cursors (prev: Numpad-Standard)
									DEVICE_KEYBOARD,	// Numpad (prev: Numpad-Centering)
									DEVICE_MOUSE,
									DEVICE_JOYSTICK_THUMBSTICK2 };

// Key pad [1..9]; Key pad 0,Key pad '.'; Left ALT,Right ALT
enum JOYKEY {	JK_DOWNLEFT=0,
				JK_DOWN,
				JK_DOWNRIGHT,
				JK_LEFT,
				JK_CENTRE,
				JK_RIGHT,
				JK_UPLEFT,
				JK_UP,
				JK_UPRIGHT,
				JK_BUTTON0,
				JK_BUTTON1,
				JK_OPENAPPLE,
				JK_CLOSEDAPPLE,
				JK_MAX
			};

const UINT PDL_MIN = 0;
const UINT PDL_CENTRAL = 127;
const UINT PDL_MAX = 255;

static BOOL  keydown[JK_MAX] = {FALSE};
static POINT keyvalue[9] = {{PDL_MIN,PDL_MAX},    {PDL_CENTRAL,PDL_MAX},    {PDL_MAX,PDL_MAX},
                            {PDL_MIN,PDL_CENTRAL},{PDL_CENTRAL,PDL_CENTRAL},{PDL_MAX,PDL_CENTRAL},
                            {PDL_MIN,PDL_MIN},    {PDL_CENTRAL,PDL_MIN},    {PDL_MAX,PDL_MIN}};

static BOOL  joybutton[3]   = {0,0,0};

static int   joyshrx[2]     = {8,8};
static int   joyshry[2]     = {8,8};
static int   joysubx[2]     = {0,0};
static int   joysuby[2]     = {0,0};

// Value persisted to Registry for REGVALUE_JOYSTICK0_EMU_TYPE
static uint32_t joytype[2]            = {J0C_JOYSTICK1, J1C_DISABLED};	// Emulation Type for joysticks #0 & #1

static BOOL  setbutton[3]   = {0,0,0};	// Used when a mouse button is pressed/released

static int   xpos[2]        = { PDL_MAX,PDL_MAX };
static int   ypos[2]        = { PDL_MAX,PDL_MAX };

static UINT64 g_paddleInactiveCycle[4] = { 0 };	// Abs cycle that each paddle becomes inactive after PTRIG strobe

static short g_nPdlTrimX = 0;
static short g_nPdlTrimY = 0;

enum {JOYPORT_LEFTRIGHT=0, JOYPORT_UPDOWN=1};

static UINT g_bJoyportEnabled = 0;	// Set to use Joyport to drive the 3 button inputs
static UINT g_uJoyportActiveStick = 0;
static UINT g_uJoyportReadMode = JOYPORT_LEFTRIGHT;

static bool g_bHookAltKeys = true;

static int JOYSTICK_1 = -1;
static int JOYSTICK_2 = -1;

//===========================================================================

void JoySetHookAltKeys(bool hook)
{
	g_bHookAltKeys = hook;
}

int GetJoystick1(void)
{
	return JOYSTICK_1;
}

int GetJoystick2(void)
{
	return JOYSTICK_2;
}

//===========================================================================
static void CheckJoystick0()
{
	if (JOYSTICK_1 < 0)
		return;

  static uint32_t lastcheck = 0;
  uint32_t currtime = GetTickCount();
  if ((currtime-lastcheck >= 10) || joybutton[0] || joybutton[1])
  {
    lastcheck = currtime;
    JOYINFO info;
    if (joyGetPos(JOYSTICK_1,&info) == JOYERR_NOERROR)
	{
      joybutton[0] = ((info.wButtons & JOY_BUTTON1) != 0);
      if (joyinfo[joytype[1]] == DEVICE_NONE)	// Only consider 2nd button if NOT emulating a 2nd Apple joystick
        joybutton[1] = ((info.wButtons & JOY_BUTTON2) != 0);

      xpos[0] = (info.wXpos-joysubx[0]) >> joyshrx[0];
      ypos[0] = (info.wYpos-joysuby[0]) >> joyshry[0];

	  // NB. This does not work for analogue joysticks (not self-centreing) - except if Trim=0
	  if(xpos[0] == 127 || xpos[0] == 128) xpos[0] += g_nPdlTrimX;
	  if(ypos[0] == 127 || ypos[0] == 128) ypos[0] += g_nPdlTrimY;
    }
  }
}

static void CheckJoystick1()
{
  static uint32_t lastcheck = 0;
  uint32_t currtime = GetTickCount();
  if ((currtime-lastcheck >= 10) || joybutton[2])
  {
    lastcheck = currtime;
    JOYINFO info;
    MMRESULT result = JOYERR_NOERROR;
    if (joyinfo[joytype[1]] == DEVICE_JOYSTICK_THUMBSTICK2)
    {
      // Use results of joystick 1 thumbstick 2 and button 2 for joystick 1 and button 1
      JOYINFOEX infoEx;
      infoEx.dwSize = sizeof(infoEx);
      infoEx.dwFlags = JOY_RETURNBUTTONS | JOY_RETURNZ | JOY_RETURNR;
      result = joyGetPosEx(JOYSTICK_1, &infoEx);
      if (result == JOYERR_NOERROR)
      {
        info.wButtons = (infoEx.dwButtons & JOY_BUTTON2) ? JOY_BUTTON1 : 0;
        info.wXpos = infoEx.dwZpos;
        info.wYpos = infoEx.dwRpos;
      }
    }
	else
	{
	  result = joyGetPos(JOYSTICK_2, &info);	// NB. joyGetPos(-1, &info) returns JOYERR_PARMS (bad parameters)
	}

	if (result == JOYERR_NOERROR)
	{
      joybutton[2] = ((info.wButtons & JOY_BUTTON1) != 0);
      if(joyinfo[joytype[1]] != DEVICE_NONE)
        joybutton[1] = ((info.wButtons & JOY_BUTTON1) != 0);	// Re-map this button when emulating a 2nd Apple joystick

      xpos[1] = (info.wXpos-joysubx[1]) >> joyshrx[1];
      ypos[1] = (info.wYpos-joysuby[1]) >> joyshry[1];

	  // NB. This does not work for analogue joysticks (not self-centreing) - except if Trim=0
	  if(xpos[1] == 127 || xpos[1] == 128) xpos[1] += g_nPdlTrimX;
	  if(ypos[1] == 127 || ypos[1] == 128) ypos[1] += g_nPdlTrimY;
    }
  }
}

//
// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE -----
//

//===========================================================================
void JoyInitialize()
{
	//
	// Detect First and Second connected JOYSTICK in WinMM API. JOYSTICKID1 == 0 but is not always the first connected joystick.
	//

	JOYSTICK_1 = -1;
	JOYSTICK_2 = -1;

	bool firstFound = false;

	const UINT numDevs = joyGetNumDevs();
	for (UINT i = 0; i < numDevs; i++)
	{
		JOYCAPS caps;
		int ret = joyGetDevCaps(i, &caps, sizeof(JOYCAPS));
		if (ret != JOYERR_NOERROR)
			continue;

		JOYINFO info;
		ret = joyGetPos(i, &info);
		if (ret != JOYERR_NOERROR)
			continue;

		if (firstFound)
		{
			JOYSTICK_2 = i;
			break;
		}

		JOYSTICK_1 = i;
		firstFound = true;
	}

	//
	// Init for emulated joystick #0:
	//

	if (joyinfo[joytype[0]] == DEVICE_JOYSTICK)
	{
		JOYCAPS caps;
		if (JOYSTICK_1 >= 0 && joyGetDevCaps(JOYSTICK_1, &caps, sizeof(JOYCAPS)) == JOYERR_NOERROR)
		{
			joyshrx[0] = 0;
			joyshry[0] = 0;
			joysubx[0] = (int)caps.wXmin;
			joysuby[0] = (int)caps.wYmin;
			UINT xrange = caps.wXmax - caps.wXmin;
			UINT yrange = caps.wYmax - caps.wYmin;
			while (xrange > 256)
			{
				xrange >>= 1;
				++joyshrx[0];
			}
			while (yrange > 256)
			{
				yrange >>= 1;
				++joyshry[0];
			}
		}
		else
		{
			joytype[0] = J0C_KEYBD_NUMPAD;
		}
	}

	//
	// Init for emulated joystick #1:
	//

	if (joyinfo[joytype[1]] == DEVICE_JOYSTICK)
	{
		JOYCAPS caps;
		if (JOYSTICK_2 >= 0 && joyGetDevCaps(JOYSTICK_2, &caps, sizeof(JOYCAPS)) == JOYERR_NOERROR)
		{
			joyshrx[1] = 0;
			joyshry[1] = 0;
			joysubx[1] = (int)caps.wXmin;
			joysuby[1] = (int)caps.wYmin;
			UINT xrange = caps.wXmax - caps.wXmin;
			UINT yrange = caps.wYmax - caps.wYmin;
			while (xrange > 256)
			{
				xrange >>= 1;
				++joyshrx[1];
			}
			while (yrange > 256)
			{
				yrange >>= 1;
				++joyshry[1];
			}
		}
		else
		{
			joytype[1] = J1C_DISABLED;
		}
	}
	else if (joyinfo[joytype[1]] == DEVICE_JOYSTICK_THUMBSTICK2)
	{
		JOYCAPS caps;
		if (JOYSTICK_1 >= 0 && joyGetDevCaps(JOYSTICK_1, &caps, sizeof(JOYCAPS)) == JOYERR_NOERROR)
		{
			joyshrx[1] = 0;
			joyshry[1] = 0;
			joysubx[1] = (int)caps.wZmin;
			joysuby[1] = (int)caps.wRmin;
			UINT xrange = caps.wZmax - caps.wZmin;
			UINT yrange = caps.wRmax - caps.wRmin;
			while (xrange > 256)
			{
				xrange >>= 1;
				++joyshrx[1];
			}
			while (yrange > 256)
			{
				yrange >>= 1;
				++joyshry[1];
			}
		}
		else
		{
			joytype[1] = J1C_DISABLED;
		}
	}
}

//===========================================================================

static UINT g_buttonVirtKey[2] = { VK_MENU, VK_MENU | KF_EXTENDED };	// VK_MENU == ALT Key

void JoySetButtonVirtualKey(UINT button, UINT virtKey)
{
	_ASSERT(button < 2);
	if (button >= 2) return;
	g_buttonVirtKey[button] = virtKey;
}

//===========================================================================

#define SUPPORT_CURSOR_KEYS

BOOL JoyProcessKey(int virtkey, bool extended, bool down, bool autorep)
{
	static struct
	{
		UINT32 Left:1;
		UINT32 Up:1;
		UINT32 Right:1;
		UINT32 Down:1;
	} CursorKeys = {0};

	const UINT virtKeyWithExtended = ((UINT)virtkey) | (extended ? KF_EXTENDED : 0);

	if ( (joyinfo[joytype[0]] != DEVICE_KEYBOARD) &&
		 (joyinfo[joytype[1]] != DEVICE_KEYBOARD) &&
		 (virtKeyWithExtended != g_buttonVirtKey[0]) &&
		 (virtKeyWithExtended != g_buttonVirtKey[1]) )
	{
		return 0;
	}

	if (!g_bHookAltKeys && virtkey == VK_MENU)				// GH#583
		return 0;

	//

	BOOL keychange = 0;
	bool bIsCursorKey = false;

	if (virtKeyWithExtended == g_buttonVirtKey[0])
	{
		keychange = 1;
		keydown[JK_OPENAPPLE] = down;
	}
	else if (virtKeyWithExtended == g_buttonVirtKey[1])
	{
		keychange = 1;
		keydown[JK_CLOSEDAPPLE] = down;
	}
	else if (!extended)
	{
		if (JoyUsingKeyboardNumpad())
		{
			keychange = 1;

			if ((virtkey >= VK_NUMPAD1) && (virtkey <= VK_NUMPAD9))		// NumLock on
			{
				keydown[virtkey-VK_NUMPAD1] = down;
			}
			else														// NumLock off (except for '0' and '.')
			{
				switch (virtkey)
				{
				case VK_END:     keydown[JK_DOWNLEFT] = down;	break;
				case VK_DOWN:    keydown[JK_DOWN] = down;		break;
				case VK_NEXT:    keydown[JK_DOWNRIGHT] = down;	break;
				case VK_LEFT:    keydown[JK_LEFT] = down;		break;
				case VK_CLEAR:   keydown[JK_CENTRE] = down;		break;
				case VK_RIGHT:   keydown[JK_RIGHT] = down;		break;
				case VK_HOME:    keydown[JK_UPLEFT] = down;		break;
				case VK_UP:      keydown[JK_UP] = down;			break;
				case VK_PRIOR:   keydown[JK_UPRIGHT] = down;	break;
				case VK_INSERT:  // fall through... (NB. extended=0 for NumPad's Insert)
				case VK_NUMPAD0: keydown[JK_BUTTON0] = down;	break;	// NumLock on
				case VK_DELETE:  // fall through... (NB. extended=0 for NumPad's Delete)
				case VK_DECIMAL: keydown[JK_BUTTON1] = down;	break;	// NumLock on
				default:         keychange = 0;					break;
				}
			}
		}
	}
#ifdef SUPPORT_CURSOR_KEYS
	else if (extended)
	{
		if (JoyUsingKeyboardCursors() && (virtkey == VK_LEFT || virtkey == VK_UP || virtkey == VK_RIGHT || virtkey == VK_DOWN))
		{
			keychange = 1;	// This prevents cursors keys being available to the Apple II (eg. Lode Runner uses cursor left/right for game speed & Ctrl-J/K for joystick/keyboard)
			bIsCursorKey = true;

			switch (virtkey)
			{
			case VK_LEFT:	CursorKeys.Left = down;		break;
			case VK_UP:		CursorKeys.Up = down;		break;
			case VK_RIGHT:	CursorKeys.Right = down;	break;
			case VK_DOWN:	CursorKeys.Down = down;		break;
			}
		}
	}
#endif

	if (!keychange)
		return 0;

	//

	if ((down && !autorep) || (GetPropertySheet().GetJoystickCenteringControl() == JOYSTICK_MODE_CENTERING))
	{
		int xkeys  = 0;
		int ykeys  = 0;
		int xtotal = 0;
		int ytotal = 0;

		for (int keynum = JK_DOWNLEFT; keynum <= JK_UPRIGHT; keynum++)
		{
			if (keydown[keynum])
			{
				if ((keynum % 3) != 1)	// Not middle col (ie. not VK_DOWN, VK_CLEAR, VK_UP)
				{
					xkeys++;
					xtotal += keyvalue[keynum].x;
				}
				if ((keynum / 3) != 1)	// Not middle row (ie. not VK_LEFT, VK_CLEAR, VK_RIGHT)
				{
					ykeys++;
					ytotal += keyvalue[keynum].y;
				}
			}
		}

		if (CursorKeys.Left)
		{
			xkeys++; xtotal += keyvalue[JK_LEFT].x;
		}
		if (CursorKeys.Right)
		{
			xkeys++; xtotal += keyvalue[JK_RIGHT].x;
		}
		if (CursorKeys.Up)
		{
			ykeys++; ytotal += keyvalue[JK_UP].y;
		}
		if (CursorKeys.Down)
		{
			ykeys++; ytotal += keyvalue[JK_DOWN].y;
		}

		// Joystick # which is being emulated by keyboard
		int nJoyNum = (joyinfo[joytype[0]] == DEVICE_KEYBOARD) ? 0 : 1;

		if (xkeys)
			xpos[nJoyNum] = xtotal / xkeys;
		else
			xpos[nJoyNum] = PDL_CENTRAL + g_nPdlTrimX;

		if (ykeys)
			ypos[nJoyNum] = ytotal / ykeys;
		else
			ypos[nJoyNum] = PDL_CENTRAL + g_nPdlTrimY;
	}

	if (bIsCursorKey && GetPropertySheet().GetJoystickCursorControl())
	{
		// Allow AppleII keyboard to see this cursor keypress too
		return 0;
	}

	return 1;
}

//===========================================================================

static void DoAutofire(UINT uButton, BOOL& pressed)
{
	static BOOL toggle[3] = {0,0,0};
	static BOOL lastPressed[3] = {0,0,0};

	BOOL nowPressed = pressed;
	if (GetPropertySheet().GetAutofire(uButton) && pressed)
	{
		toggle[uButton] = (!lastPressed[uButton]) ? TRUE : !toggle[uButton];
		pressed = pressed && toggle[uButton];
	}
	lastPressed[uButton] = nowPressed;
}

BYTE __stdcall JoyportReadButton(WORD address, ULONG nExecutedCycles)
{
	BOOL pressed = 0;

	if (g_uJoyportActiveStick == 0)
	{
		switch (address)
		{
			case 0x61:
				pressed = (joybutton[0] || setbutton[0] /*|| keydown[JK_OPENAPPLE]*/);
				if(joyinfo[joytype[1]] != DEVICE_KEYBOARD)	// BUG? joytype[1] should be [0] ?
					pressed = (pressed || keydown[JK_BUTTON0]);
				break;

			case 0x62:	// Left or Up
				if (g_uJoyportReadMode == JOYPORT_LEFTRIGHT)	// LEFT
				{
					if (xpos[0] == 0)	// TODO: More range for mouse control?
						pressed = 1;
				}
				else	// UP
				{
					if (ypos[0] == 0)	// TODO: More range for mouse control?
						pressed = 1;
				}
				break;

			case 0x63:	// Right or Down
				if (g_uJoyportReadMode == JOYPORT_LEFTRIGHT)	// RIGHT
				{
					if (xpos[0] >= 255)	// TODO: More range for mouse control?
						pressed = 1;
				}
				else	// DOWN
				{
					if (ypos[0] >= 255)	// TODO: More range for mouse control?
						pressed = 1;
				}
				break;
		}
	}
	else	// TODO: stick #1
	{
	}

	pressed = pressed ? 0 : 1;	// Invert as Joyport signals are active low

	return MemReadFloatingBus(pressed, nExecutedCycles);
}

static BOOL CheckButton0Pressed(void)
{
	BOOL pressed =	joybutton[0] ||
					setbutton[0] ||
					keydown[JK_OPENAPPLE];

	if (joyinfo[joytype[1]] != DEVICE_KEYBOARD)	// NB. always joytype[1] regardless if button is 0 or 1
		pressed = pressed || keydown[JK_BUTTON0];

	return pressed;
}

static BOOL CheckButton1Pressed(void)
{
	BOOL pressed =	joybutton[1] ||
					setbutton[1] ||
					keydown[JK_CLOSEDAPPLE];

	if (joyinfo[joytype[1]] != DEVICE_KEYBOARD)	// NB. always joytype[1] regardless if button is 0 or 1
		pressed = pressed || keydown[JK_BUTTON1];

	return pressed;
}

BYTE __stdcall JoyReadButton(WORD pc, WORD address, BYTE, BYTE, ULONG nExecutedCycles)
{
	address &= 0xFF;

	if(joyinfo[joytype[0]] == DEVICE_JOYSTICK)
		CheckJoystick0();
	if((joyinfo[joytype[1]] == DEVICE_JOYSTICK) || (joyinfo[joytype[1]] == DEVICE_JOYSTICK_THUMBSTICK2))
		CheckJoystick1();

	if (g_bJoyportEnabled)
	{
		// Some extra logic to stop the Joyport forcing a self-test at CTRL+RESET
		if ((address != 0x62) || (address == 0x62 && pc != 0xC242 && pc != 0xC2BE))	// Original //e ($C242), Enhanced //e ($C2BE) 
			return JoyportReadButton(address, nExecutedCycles);
	}

	const bool swapButtons0and1 = GetPropertySheet().GetButtonsSwapState();

	BOOL pressed = FALSE;
	switch (address)
	{
		case 0x61:
			{
				pressed = !swapButtons0and1 ? CheckButton0Pressed() : CheckButton1Pressed();
				const UINT button0 = !swapButtons0and1 ? 0 : 1;
				DoAutofire(button0, pressed);
				if (CopyProtectionDonglePB0() >= 0)				//If a copy protection dongle needs PB0, this overrides the joystick
					pressed = CopyProtectionDonglePB0();
			}
			break;

		case 0x62:
			{
				pressed = !swapButtons0and1 ? CheckButton1Pressed() : CheckButton0Pressed();
				const UINT button1 = !swapButtons0and1 ? 1 : 0;
				DoAutofire(button1, pressed);
				if (CopyProtectionDonglePB1() >= 0)				//If a copy protection dongle needs PB1, this overrides the joystick
					pressed = CopyProtectionDonglePB1();
			}
			break;

		case 0x63:
			if (CopyProtectionDonglePB2() >= 0)					//If a copy protection dongle needs PB2, this overrides the joystick
				pressed = CopyProtectionDonglePB2();
			else if (IS_APPLE2 && (joyinfo[joytype[1]] == DEVICE_NONE))
			{
				// Apple II/II+ with no joystick has the "SHIFT key mod"
				// See Sather's Understanding The Apple II p7-36
				pressed = !(GetKeyState(VK_SHIFT) < 0);
			}
			else
			{
				pressed = (joybutton[2] || setbutton[2]);
				DoAutofire(2, pressed);
			}
			break;
	}

	return MemReadFloatingBus(pressed, nExecutedCycles);
}

//===========================================================================

// PREAD:		; $FB1E
//  AD 70 C0 : (4) LDA $C070
//  A0 00    : (2) LDA #$00
//  EA       : (2) NOP
//  EA       : (2) NOP
// Lbl1:						; 11 cycles is the normal duration of the loop
//  BD 64 C0 : (4) LDA $C064,X
//  10 04    : (2) BPL Lbl2		; NB. 3 cycles if branch taken (not likely)
//  C8       : (2) INY
//  D0 F8    : (3) BNE Lbl1		; NB. 2 cycles if branch not taken (not likely)
//  88       : (2) DEY
// Lbl2:
//  60       : (6) RTS
//

static const double PDL_CNTR_INTERVAL = 2816.0 / 255.0;	// 11.04 (From KEGS)

BYTE __stdcall JoyReadPosition(WORD programcounter, WORD address, BYTE, BYTE, ULONG nExecutedCycles)
{
	CpuCalcCycles(nExecutedCycles);

	BOOL nPdlCntrActive = g_nCumulativeCycles <= g_paddleInactiveCycle[address & 3];

	// If no joystick connected, then this is always active (GH#778) && no copy-protection dongle connected
	const UINT joyNum = (address & 2) ? 1 : 0;	// $C064..$C067
	if (joyinfo[joytype[joyNum]] == DEVICE_NONE && CopyProtectionDonglePDL(address & 3) < 0)
		nPdlCntrActive = TRUE;

	return MemReadFloatingBus(nPdlCntrActive, nExecutedCycles);
}

//===========================================================================
void JoyReset()
{
  int loop = 0;
  while (loop < JK_MAX)
    keydown[loop++] = FALSE;
}

//===========================================================================

static void SetPaddleInactiveCycle(UINT pdl, UINT pdlPos)
{
	g_paddleInactiveCycle[pdl] = g_nCumulativeCycles + (UINT64)((double)pdlPos * PDL_CNTR_INTERVAL);
}

void JoyResetPosition(ULONG nExecutedCycles)
{
	CpuCalcCycles(nExecutedCycles);

	bool isJoystick[2] = { false, false };

	if (joyinfo[joytype[0]] == DEVICE_JOYSTICK)
	{
		CheckJoystick0();
		isJoystick[0] = true;
	}
	if (joyinfo[joytype[1]] == DEVICE_JOYSTICK || joyinfo[joytype[1]] == DEVICE_JOYSTICK_THUMBSTICK2)
	{
		CheckJoystick1();
		isJoystick[1] = true;
	}

	// If any of the timers are still running then strobe has no effect (GH#985)
	for (UINT pdl = 0; pdl < 4; pdl++)
	{
		if (g_nCumulativeCycles <= g_paddleInactiveCycle[pdl])
			continue;

		const UINT joyNum = (pdl & 2) ? 1 : 0;
		UINT pdlPos = (pdl & 1) ? ypos[joyNum] : xpos[joyNum];

		// "Square the circle" for controllers with analog sticks (but compatible with digital D-pads too) - GH#429
		if (isJoystick[joyNum])
		{
			// Convert to unit circle, centred at (0,0)
			const double scalar = 0.5 * 255.0;
			const double offset = 1.0;
			const double x = ((double)xpos[joyNum]) / scalar - offset;
			const double y = ((double)ypos[joyNum]) / scalar - offset;
			double axis = !(pdl & 1) ? x : y;

			if (x * y != 0.0)
			{
				// rescale the circle to the square
				const double ratio2 = (y * y) / (x * x);
				const double c = min(ratio2, 1.0 / ratio2);
				const double coeff = sqrt(1.0 + c);
				axis *= coeff;
			}

			if (axis < -1.0) axis = -1.0;
			else if (axis > 1.0) axis = 1.0;

			pdlPos = static_cast<int>((axis + offset) * scalar);
		}

		// This is from KEGS. It helps games like Championship Lode Runner, Boulderdash & Learning with Leeper(GH#1128)
		if (pdlPos >= 255)
			pdlPos = 287;

		SetPaddleInactiveCycle(pdl, pdlPos);
	}

	// Protection dongle overrides the PDL timer
	// . eg. needed when Robocom PDL3 is still timing-out from the extended-255 count (eg. 287)
	// . really if it were at 255 (ie. not connected), then on enabling the dongle it switches to (eg) 23 it should timeout immediately
	for (UINT pdl = 0; pdl < 4; pdl++)
	{
		int pdlPosDongle = CopyProtectionDonglePDL(pdl);
		if (pdlPosDongle >= 0)
			SetPaddleInactiveCycle(pdl, pdlPosDongle);
	}
}

//===========================================================================

// Called when mouse is being used as a joystick && mouse button changes
void JoySetButton(eBUTTON number, eBUTTONSTATE down)
{
  if (number > 1)	// Sanity check on mouse button #
    return;

  // If 2nd joystick is enabled, then both joysticks only have 1 button
  if((joyinfo[joytype[1]] != DEVICE_NONE) && (number != 0))
	  return;

  // If it is 2nd joystick that is being emulated with mouse, then re-map button #
  if(joyinfo[joytype[1]] == DEVICE_MOUSE)
  {
	number = BUTTON1;	// 2nd joystick controls Apple button #1
  }

  setbutton[number] = down;
}

//===========================================================================
BOOL JoySetEmulationType(HWND window, uint32_t newtype, int nJoystickNumber, const bool bMousecardActive)
{
  if(joytype[nJoystickNumber] == newtype)
	  return 1;	// Already set to this type. Return OK.

  if (joyinfo[newtype] == DEVICE_JOYSTICK || joyinfo[newtype] == DEVICE_JOYSTICK_THUMBSTICK2)
  {
    JOYCAPS caps;
	int nJoy2ID = joyinfo[newtype] == DEVICE_JOYSTICK_THUMBSTICK2 ? JOYSTICK_1 : JOYSTICK_2;
	int nJoyID = nJoystickNumber == JN_JOYSTICK0 ? JOYSTICK_1 : nJoy2ID;
    if (nJoyID < 0 || joyGetDevCaps(nJoyID, &caps, sizeof(JOYCAPS)) != JOYERR_NOERROR)
    {
      MessageBox(window,
                 "The emulator is unable to read your PC joystick.  "
                 "Ensure that your game port is configured properly, "
                 "that the joystick is firmly plugged in, and that "
                 "you have a joystick driver installed.",
                 "Configuration",
                 MB_ICONEXCLAMATION | MB_SETFOREGROUND);
      return 0;
    }
    if ((joyinfo[newtype] == DEVICE_JOYSTICK_THUMBSTICK2) && (caps.wNumAxes < 4))
    {
      MessageBox(window,
                 "The emulator is unable to read thumbstick 2.  "
                 "Ensure that your game port is configured properly, "
                 "that the joystick is firmly plugged in, and that "
                 "you have a joystick driver installed.",
                 "Configuration",
                 MB_ICONEXCLAMATION | MB_SETFOREGROUND);
      return 0;
    }
  }
  else if ((joyinfo[newtype] == DEVICE_MOUSE) &&
           (joyinfo[joytype[nJoystickNumber]] != DEVICE_MOUSE))
  {
	if (bMousecardActive)
	{
		// Shouldn't be necessary, since Property Sheet's logic should prevent this option being given to the user.
	  MessageBox(window,
				 "Mouse interface card is enabled - unable to use mouse for joystick emulation.",
				 "Configuration",
				 MB_ICONEXCLAMATION | MB_SETFOREGROUND);
	  return 0;
	}

    MessageBox(window,
               "To begin emulating a joystick with your mouse, move "
               "the mouse cursor over the emulated screen of a running "
               "program and click the left mouse button.  During the "
               "time the mouse is emulating a joystick, you will not "
               "be able to use it to perform mouse functions, and the "
               "mouse cursor will not be visible.  To end joystick "
               "emulation and regain the mouse cursor, click the left "
               "mouse button while pressing Ctrl.",
               "Configuration",
               MB_ICONINFORMATION | MB_SETFOREGROUND);
  }
  else if (joyinfo[newtype] == DEVICE_KEYBOARD)
  {
	  if (newtype == J0C_KEYBD_CURSORS || newtype == J1C_KEYBD_CURSORS)
	  {
			MessageBox(window,
						"Using cursor keys to emulate a joystick can cause conflicts.\n\n"
						"Be aware that 'cursor-up' = CTRL+K, and 'cursor-down' = CTRL+J.\n"
						"EG. Lode Runner uses CTRL+K/J to switch between keyboard/joystick modes "
						"(and cursor-left/right to control speed).\n\n"
						"Also if cursor keys are blocked from being read from the Apple keyboard "
						"then even simple AppleSoft command-line editing (cursor left/right) will not work.",
						"Configuration",
						MB_ICONINFORMATION | MB_SETFOREGROUND);
	  }
  }

  joytype[nJoystickNumber] = newtype;
  JoyInitialize();
  JoyReset();
  return 1;
}


//===========================================================================

// Called when mouse is being used as a joystick && mouse position changes
void JoySetPosition(int xvalue, int xrange, int yvalue, int yrange)
{
  int nJoyNum = (joyinfo[joytype[0]] == DEVICE_MOUSE) ? 0 : 1;
  xpos[nJoyNum] = (xvalue*255)/xrange;
  ypos[nJoyNum] = (yvalue*255)/yrange;
}
 
//===========================================================================

BOOL JoyUsingMouse()
{
	return (joyinfo[joytype[0]] == DEVICE_MOUSE) || (joyinfo[joytype[1]] == DEVICE_MOUSE);
}

BOOL JoyUsingKeyboard()
{
	return (joyinfo[joytype[0]] == DEVICE_KEYBOARD) || (joyinfo[joytype[1]] == DEVICE_KEYBOARD);
}

BOOL JoyUsingKeyboardCursors()
{
	return (joytype[0] == J0C_KEYBD_CURSORS) || (joytype[1] == J1C_KEYBD_CURSORS);
}

BOOL JoyUsingKeyboardNumpad()
{
	return (joytype[0] == J0C_KEYBD_NUMPAD) || (joytype[1] == J1C_KEYBD_NUMPAD);
}

//===========================================================================

void JoyDisableUsingMouse()
{
	if (joyinfo[joytype[0]] == DEVICE_MOUSE)
		joytype[0] = J0C_DISABLED;

	if (joyinfo[joytype[1]] == DEVICE_MOUSE)
		joytype[1] = J1C_DISABLED;
}

//===========================================================================

void JoySetJoyType(UINT num, uint32_t type)
{
	_ASSERT(num <= JN_JOYSTICK1);
	if (num > JN_JOYSTICK1)
		return;

	if (num == JN_JOYSTICK0)	// GH#434
	{
		_ASSERT(type < J0C_MAX);
		if (type >= J0C_MAX)
			return;
	}
	else
	{
		_ASSERT(type < J1C_MAX);
		if (type >= J1C_MAX)
			return;
	}

	joytype[num] = type;

	// Refresh centre positions whenever 'joytype' changes
	JoySetTrim(JoyGetTrim(true) , true);
	JoySetTrim(JoyGetTrim(false), false);
}

uint32_t JoyGetJoyType(UINT num)
{
	_ASSERT(num <= JN_JOYSTICK1);
	if (num > JN_JOYSTICK1)
		return J0C_DISABLED;

	return joytype[num];
}

//===========================================================================

void JoySetTrim(short nValue, bool bAxisX)
{
	if(bAxisX)
		g_nPdlTrimX = nValue;
	else
		g_nPdlTrimY = nValue;

	int nJoyNum = -1;

	if(joyinfo[joytype[0]] == DEVICE_KEYBOARD)
		nJoyNum = 0;
	else if(joyinfo[joytype[1]] == DEVICE_KEYBOARD)
		nJoyNum = 1;

	if(nJoyNum >= 0)
	{
        xpos[nJoyNum] = PDL_CENTRAL+g_nPdlTrimX;
        ypos[nJoyNum] = PDL_CENTRAL+g_nPdlTrimY;
	}
}

short JoyGetTrim(bool bAxisX)
{
	return bAxisX ? g_nPdlTrimX : g_nPdlTrimY;
}

//===========================================================================

// Joyport truth-table:
//
// AN0  AN1  /PB0       /PB1    /PB2
// ------------------------------------
//  0    0   Trigger-1  Left-1  Right-1
//  0    1   Trigger-1  Up-1    Down-1
//  1    0   Trigger-2  Left-2  Right-2
//  1    1   Trigger-2  Up-2    Down-2

#if 0
void JoyportEnable(const bool bEnable)
{
	if (IS_APPLE2C())
		g_bJoyportEnabled = false;
	else
		g_bJoyportEnabled = bEnable ? 1 : 0;
}
#endif

void JoyportControl(const UINT uControl)
{
	if (!g_bJoyportEnabled)
		return;

	switch (uControl)
	{
	case 0:	// AN0 clr
		g_uJoyportActiveStick = 0;
		break;
	case 1:	// AN0 set
		g_uJoyportActiveStick = 1;
		break;
	case 2:	// AN1 clr
		g_uJoyportReadMode = JOYPORT_LEFTRIGHT;
		break;
	case 3:	// AN1 set
		g_uJoyportReadMode = JOYPORT_UPDOWN;
		break;
	}
}

//===========================================================================

#define SS_YAML_KEY_COUNTERRESETCYCLE "Counter Reset Cycle"
#define SS_YAML_KEY_JOY0TRIMX "Joystick0 TrimX"
#define SS_YAML_KEY_JOY0TRIMY "Joystick0 TrimY"
#define SS_YAML_KEY_JOY1TRIMX "Joystick1 TrimX"
#define SS_YAML_KEY_JOY1TRIMY "Joystick1 TrimY"
#define SS_YAML_KEY_PDL_INACTIVE_CYCLE "Paddle%1d Inactive Cycle"

static const std::string& JoyGetSnapshotStructName(void)
{
	static const std::string name("Joystick");
	return name;
}

void JoySaveSnapshot(YamlSaveHelper& yamlSaveHelper)
{
	YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", JoyGetSnapshotStructName().c_str());
	yamlSaveHelper.SaveInt(SS_YAML_KEY_JOY0TRIMX, JoyGetTrim(true));
	yamlSaveHelper.SaveInt(SS_YAML_KEY_JOY0TRIMY, JoyGetTrim(false));
	yamlSaveHelper.Save("%s: %d # not implemented yet\n", SS_YAML_KEY_JOY1TRIMX, 0);	// not implemented yet
	yamlSaveHelper.Save("%s: %d # not implemented yet\n", SS_YAML_KEY_JOY1TRIMY, 0);	// not implemented yet

	for (UINT n = 0; n < 4; n++)
	{
		std::string str = StrFormat(SS_YAML_KEY_PDL_INACTIVE_CYCLE, n);
		yamlSaveHelper.SaveHexUint64(str.c_str(), g_paddleInactiveCycle[n]);
	}
}

void JoyLoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
{
	if (!yamlLoadHelper.GetSubMap(JoyGetSnapshotStructName()))
		return;

	JoySetTrim(yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY0TRIMX), true);
	JoySetTrim(yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY0TRIMY), false);
	yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY1TRIMX);	// dump value
	yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY1TRIMY);	// dump value

	if (version >= 7)
	{
		for (UINT n = 0; n < 4; n++)
		{
			std::string str = StrFormat(SS_YAML_KEY_PDL_INACTIVE_CYCLE, n);
			g_paddleInactiveCycle[n] = yamlLoadHelper.LoadUint64(str);
		}
	}
	else
	{
		UINT64 resetCycle = yamlLoadHelper.LoadUint64(SS_YAML_KEY_COUNTERRESETCYCLE);
		for (UINT n = 0; n < 4; n++)
			g_paddleInactiveCycle[n] = resetCycle;
	}

	yamlLoadHelper.PopMap();
}
